#include <arpa/inet.h>
#include <cerrno>
#include <fcntl.h>
#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

class TCPClient
{
private:
    int m_socketfd;
    int m_port;
    std::string m_serveraddr;

public:
    TCPClient(std::string serveraddr, int port);
    ~TCPClient();

    int Init();
    int getSocket();
    int checkReceiveData();
    int checkDataReadable();
    int recvData();

private:
    //包括创建连接和检测连接是否成功
    int asyncConnect();
    int setSocketNonblock();
    int createConnection();
    int checkConnection();
};

int TCPClient::recvData()
{
    char buffer[1024] = {0};
    int ret = recv(m_socketfd, buffer, 1024, 0);
    if (ret == 0)
    {
        std::cout << "The server is disconnected and the program exits" << std::endl;
        return -1;
    }
    if (ret < 0)
    {
        if (errno != EINTR && errno != EWOULDBLOCK)
        {
            std::cout << "Data reception error\n";
            return -1;
        }
    }
    std::cout << buffer << std::endl;
}
int TCPClient::checkDataReadable()
{
    fd_set readSet;
    FD_ZERO(&readSet);
    FD_SET(m_socketfd, &readSet);
    if (select(m_socketfd + 1, &readSet, nullptr, nullptr, nullptr) == 1)
    {
        std::cout << "Readable" << std::endl;
        return 1;
    }
    return -1;
}
int TCPClient::getSocket()
{
    return m_socketfd;
}

TCPClient::TCPClient(std::string serveraddr, int port)
    : m_serveraddr{serveraddr},
      m_port{port},
      m_socketfd{0}
{
    if (Init() == -1)
    {
        std::cout << "Initialization failure" << std::endl;
        return;
    }
}
int TCPClient::Init()
{
    m_socketfd = socket(PF_INET, SOCK_STREAM, 0);
    if (setSocketNonblock() == -1)
    {
        std::cout << "Failed to set the socket to non-blocking" << std::endl;
        return -1;
    }
    if (asyncConnect() == -1)
    {
        return -1;
    }
}

TCPClient::~TCPClient()
{
    if (m_socketfd > 0)
    {
        close(m_socketfd);
    }
}

int TCPClient::setSocketNonblock()
{
    int oldFlag = fcntl(m_socketfd, F_GETFL);
    int newFlag = oldFlag | O_NONBLOCK;
    int ret = fcntl(m_socketfd, F_SETFL, newFlag);
    return ret;
}

int TCPClient::asyncConnect()
{
    if (createConnection() == -1)
    {
        return -1;
    }
    if (checkConnection() == -1)
    {
        return -1;
    }
}
int TCPClient::createConnection()
{
RETRY:
    sockaddr_in serverAddress{};
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(m_port);
    serverAddress.sin_addr.s_addr = inet_addr(m_serveraddr.c_str());
    int ret = connect(m_socketfd, (sockaddr *)&serverAddress, sizeof(sockaddr));

    if (ret == 0)
    {
        return 1;
    }
    if (errno == EINTR)
    {
        goto RETRY;
    }
    else if (errno != EINPROGRESS)
    {
        std::cout << "Failed to connect to the server" << std::endl;
        return -1;
    }
}

int TCPClient::checkConnection()
{
    fd_set writefds;
    FD_ZERO(&writefds);
    FD_SET(m_socketfd, &writefds);

    timeval tv;
    tv.tv_sec = 5;
    tv.tv_usec = 0;

    int ret = select(m_socketfd + 1, nullptr, &writefds, nullptr, &tv);
    if (ret <= 0)
    {
        std::cout << "Connection timeout" << std::endl;
        return -1;
    }
    if (!FD_ISSET(m_socketfd, &writefds))
    {
        std::cout << "Connect error\n";
        return -1;
    }
    int error = 0;
    socklen_t length = sizeof(int);
    if (::getsockopt(m_socketfd, SOL_SOCKET, SO_ERROR, &error, &length) < 0)
    {
        std::cout << "Get socket option failed" << std::endl;
        return -1;
    }
    if (error != 0)
    {
        std::cout << "Connection failed after select\n";
        return -1;
    }

    // Connection successful
    std::cout << "Connecting to the server succeeded\n";
    return 1;
}